View Javadoc
1 /* 2 * $Header: /cvsroot/xwing/projects/xwing/src/java/org/apache/commons/beanutils/MappedPropertyDescriptor.java,v 1.1 2003/10/06 17:53:16 jshowlett Exp $ 3 * $Revision: 1.1 $ 4 * $Date: 2003/10/06 17:53:16 $ 5 * 6 * ==================================================================== 7 * 8 * The Apache Software License, Version 1.1 9 * 10 * Copyright (c) 1999-2003 The Apache Software Foundation. All rights 11 * reserved. 12 * 13 * Redistribution and use in source and binary forms, with or without 14 * modification, are permitted provided that the following conditions 15 * are met: 16 * 17 * 1. Redistributions of source code must retain the above copyright 18 * notice, this list of conditions and the following disclaimer. 19 * 20 * 2. Redistributions in binary form must reproduce the above copyright 21 * notice, this list of conditions and the following disclaimer in 22 * the documentation and/or other materials provided with the 23 * distribution. 24 * 25 * 3. The end-user documentation included with the redistribution, if 26 * any, must include the following acknowlegement: 27 * "This product includes software developed by the 28 * Apache Software Foundation (http://www.apache.org/)." 29 * Alternately, this acknowlegement may appear in the software itself, 30 * if and wherever such third-party acknowlegements normally appear. 31 * 32 * 4. The names "The Jakarta Project", "Commons", and "Apache Software 33 * Foundation" must not be used to endorse or promote products derived 34 * from this software without prior written permission. For written 35 * permission, please contact apache@apache.org. 36 * 37 * 5. Products derived from this software may not be called "Apache" 38 * nor may "Apache" appear in their names without prior written 39 * permission of the Apache Group. 40 * 41 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED 42 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 43 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 44 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR 45 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 46 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 47 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 48 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 49 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 50 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 51 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 52 * SUCH DAMAGE. 53 * ==================================================================== 54 * 55 * This software consists of voluntary contributions made by many 56 * individuals on behalf of the Apache Software Foundation. For more 57 * information on the Apache Software Foundation, please see 58 * <http://www.apache.org/>. 59 * 60 */ 61 62 package org.apache.commons.beanutils; 63 64 import java.beans.IntrospectionException; 65 import java.beans.PropertyDescriptor; 66 import java.lang.reflect.Method; 67 import java.lang.reflect.Modifier; 68 import java.security.AccessController; 69 import java.security.PrivilegedAction; 70 71 /*** 72 * A MappedPropertyDescriptor describes one mapped property. 73 * Mapped properties are multivalued properties like indexed properties 74 * but that are accessed with a String key instead of an index. 75 * Such property values are typically stored in a Map collection. 76 * For this class to work properly, a mapped value must have 77 * getter and setter methods of the form 78 * <p><code>get<strong>Property</strong>(String key)<code> and 79 * <p><code>set<Property>(String key, Object value)<code>, 80 * <p>where <code><strong>Property</strong></code> must be replaced 81 * by the name of the property. 82 * @see java.beans.PropertyDescriptor 83 * 84 * @author Rey François 85 * @author Gregor Raıman 86 * @version $Revision: 1.1 $ $Date: 2003/10/06 17:53:16 $ 87 */ 88 89 public class MappedPropertyDescriptor extends PropertyDescriptor { 90 // ----------------------------------------------------- Instance Variables 91 92 /*** 93 * The underlying data type of the property we are describing. 94 */ 95 private Class mappedPropertyType; 96 97 /*** 98 * The reader method for this property (if any). 99 */ 100 private Method mappedReadMethod; 101 102 /*** 103 * The writer method for this property (if any). 104 */ 105 private Method mappedWriteMethod; 106 107 /*** 108 * The parameter types array for the reader method signature. 109 */ 110 private static final Class[] stringClassArray = 111 new Class[] { String.class }; 112 113 // ----------------------------------------------------------- Constructors 114 115 /*** 116 * Constructs a MappedPropertyDescriptor for a property that follows 117 * the standard Java convention by having getFoo and setFoo 118 * accessor methods, with the addition of a String parameter (the key). 119 * Thus if the argument name is "fred", it will 120 * assume that the writer method is "setFred" and the reader method 121 * is "getFred". Note that the property name should start with a lower 122 * case character, which will be capitalized in the method names. 123 * 124 * @param propertyName The programmatic name of the property. 125 * @param beanClass The Class object for the target bean. For 126 * example sun.beans.OurButton.class. 127 * 128 * @exception IntrospectionException if an exception occurs during 129 * introspection. 130 */ 131 public MappedPropertyDescriptor(String propertyName, Class beanClass) 132 throws IntrospectionException { 133 134 super(propertyName, null, null); 135 136 if (propertyName == null || propertyName.length() == 0) { 137 throw new IntrospectionException( 138 "bad property name: " 139 + propertyName 140 + " on class: " 141 + beanClass.getClass().getName()); 142 } 143 144 setName(propertyName); 145 String base = capitalizePropertyName(propertyName); 146 147 // Look for mapped read method and matching write method 148 try { 149 mappedReadMethod = 150 findMethod(beanClass, "get" + base, 1, stringClassArray); 151 Class params[] = { String.class, mappedReadMethod.getReturnType()}; 152 mappedWriteMethod = findMethod(beanClass, "set" + base, 2, params); 153 } catch (IntrospectionException e) { 154 ; 155 } 156 157 // If there's no read method, then look for just a write method 158 if (mappedReadMethod == null) { 159 mappedWriteMethod = findMethod(beanClass, "set" + base, 2); 160 } 161 162 if ((mappedReadMethod == null) && (mappedWriteMethod == null)) { 163 throw new IntrospectionException( 164 "Property '" 165 + propertyName 166 + "' not found on " 167 + beanClass.getName()); 168 } 169 170 findMappedPropertyType(); 171 } 172 173 /*** 174 * This constructor takes the name of a mapped property, and method 175 * names for reading and writing the property. 176 * 177 * @param propertyName The programmatic name of the property. 178 * @param beanClass The Class object for the target bean. For 179 * example sun.beans.OurButton.class. 180 * @param mappedGetterName The name of the method used for 181 * reading one of the property values. May be null if the 182 * property is write-only. 183 * @param mappedSetterName The name of the method used for writing 184 * one of the property values. May be null if the property is 185 * read-only. 186 * 187 * @exception IntrospectionException if an exception occurs during 188 * introspection. 189 */ 190 public MappedPropertyDescriptor( 191 String propertyName, 192 Class beanClass, 193 String mappedGetterName, 194 String mappedSetterName) 195 throws IntrospectionException { 196 197 super(propertyName, null, null); 198 199 if (propertyName == null || propertyName.length() == 0) { 200 throw new IntrospectionException( 201 "bad property name: " + propertyName); 202 } 203 setName(propertyName); 204 205 // search the mapped get and set methods 206 mappedReadMethod = 207 findMethod(beanClass, mappedGetterName, 1, stringClassArray); 208 209 if (mappedReadMethod != null) { 210 Class params[] = { String.class, mappedReadMethod.getReturnType()}; 211 mappedWriteMethod = 212 findMethod(beanClass, mappedSetterName, 2, params); 213 } else { 214 mappedWriteMethod = findMethod(beanClass, mappedSetterName, 2); 215 } 216 217 findMappedPropertyType(); 218 } 219 220 /*** 221 * This constructor takes the name of a mapped property, and Method 222 * objects for reading and writing the property. 223 * 224 * @param propertyName The programmatic name of the property. 225 * @param mappedGetter The method used for reading one of 226 * the property values. May be be null if the property 227 * is write-only. 228 * @param mappedSetter The method used for writing one the 229 * property values. May be null if the property is read-only. 230 * 231 * @exception IntrospectionException if an exception occurs during 232 * introspection. 233 */ 234 public MappedPropertyDescriptor( 235 String propertyName, 236 Method mappedGetter, 237 Method mappedSetter) 238 throws IntrospectionException { 239 240 super(propertyName, mappedGetter, mappedSetter); 241 242 if (propertyName == null || propertyName.length() == 0) { 243 throw new IntrospectionException( 244 "bad property name: " + propertyName); 245 } 246 247 setName(propertyName); 248 mappedReadMethod = mappedGetter; 249 mappedWriteMethod = mappedSetter; 250 findMappedPropertyType(); 251 } 252 253 // -------------------------------------------------------- Public Methods 254 255 /*** 256 * Gets the Class object for the property values. 257 * 258 * @return The Java type info for the property values. Note that 259 * the "Class" object may describe a built-in Java type such as "int". 260 * The result may be "null" if this is a mapped property that 261 * does not support non-keyed access. 262 * <p> 263 * This is the type that will be returned by the mappedReadMethod. 264 */ 265 public Class getMappedPropertyType() { 266 return mappedPropertyType; 267 } 268 269 /*** 270 * Gets the method that should be used to read one of the property value. 271 * 272 * @return The method that should be used to read the property value. 273 * May return null if the property can't be read. 274 */ 275 public Method getMappedReadMethod() { 276 return mappedReadMethod; 277 } 278 279 /*** 280 * Sets the method that should be used to read one of the property value. 281 * 282 * @param getter The new getter method. 283 */ 284 public void setMappedReadMethod(Method mappedGetter) 285 throws IntrospectionException { 286 mappedReadMethod = mappedGetter; 287 findMappedPropertyType(); 288 } 289 290 /*** 291 * Gets the method that should be used to write one of the property value. 292 * 293 * @return The method that should be used to write one of the property value. 294 * May return null if the property can't be written. 295 */ 296 public Method getMappedWriteMethod() { 297 return mappedWriteMethod; 298 } 299 300 /*** 301 * Sets the method that should be used to write the property value. 302 * 303 * @param setter The new setter method. 304 */ 305 public void setMappedWriteMethod(Method mappedSetter) 306 throws IntrospectionException { 307 mappedWriteMethod = mappedSetter; 308 findMappedPropertyType(); 309 } 310 311 // ------------------------------------------------------- Private Methods 312 313 /*** 314 * Introspect our bean class to identify the corresponding getter 315 * and setter methods. 316 */ 317 private void findMappedPropertyType() throws IntrospectionException { 318 try { 319 mappedPropertyType = null; 320 if (mappedReadMethod != null) { 321 if (mappedReadMethod.getParameterTypes().length != 1) { 322 throw new IntrospectionException("bad mapped read method arg count"); 323 } 324 mappedPropertyType = mappedReadMethod.getReturnType(); 325 if (mappedPropertyType == Void.TYPE) { 326 throw new IntrospectionException( 327 "mapped read method " 328 + mappedReadMethod.getName() 329 + " returns void"); 330 } 331 } 332 333 if (mappedWriteMethod != null) { 334 Class params[] = mappedWriteMethod.getParameterTypes(); 335 if (params.length != 2) { 336 throw new IntrospectionException("bad mapped write method arg count"); 337 } 338 if (mappedPropertyType != null 339 && mappedPropertyType != params[1]) { 340 throw new IntrospectionException("type mismatch between mapped read and write methods"); 341 } 342 mappedPropertyType = params[1]; 343 } 344 } catch (IntrospectionException ex) { 345 throw ex; 346 } 347 } 348 349 /*** 350 * Return a capitalized version of the specified property name. 351 * 352 * @param s The property name 353 */ 354 private static String capitalizePropertyName(String s) { 355 if (s.length() == 0) { 356 return s; 357 } 358 359 char chars[] = s.toCharArray(); 360 chars[0] = Character.toUpperCase(chars[0]); 361 return new String(chars); 362 } 363 364 //====================================================================== 365 // Package private support methods (copied from java.beans.Introspector). 366 //====================================================================== 367 368 // Cache of Class.getDeclaredMethods: 369 private static java.util.Hashtable declaredMethodCache = 370 new java.util.Hashtable(); 371 372 /* 373 * Internal method to return *public* methods within a class. 374 */ 375 private static synchronized Method[] getPublicDeclaredMethods(Class clz) { 376 // Looking up Class.getMethods is relatively expensive, 377 // so we cache the results. 378 final Class fclz = clz; 379 Method[] result = (Method[]) declaredMethodCache.get(fclz); 380 if (result != null) { 381 return result; 382 } 383 384 // We have to raise privilege for getMethods 385 result = 386 (Method[]) AccessController.doPrivileged(new PrivilegedAction() { 387 public Object run() { 388 return fclz.getMethods(); 389 } 390 }); 391 392 // Null out any methods not belonging to us 393 for (int i = 0; i < result.length; i++) { 394 Method method = result[i]; 395 Class declClass = method.getDeclaringClass(); 396 if (declClass != fclz) { 397 result[i] = null; 398 } 399 } 400 401 // Add it to the cache. 402 declaredMethodCache.put(clz, result); 403 return result; 404 } 405 406 /*** 407 * Internal support for finding a target methodName on a given class. 408 */ 409 private static Method internalFindMethod( 410 Class start, 411 String methodName, 412 int argCount) { 413 // For overridden methods we need to find the most derived version. 414 // So we start with the given class and walk up the superclass chain. 415 for (Class cl = start; cl != null; cl = cl.getSuperclass()) { 416 Method methods[] = getPublicDeclaredMethods(cl); 417 for (int i = 0; i < methods.length; i++) { 418 Method method = methods[i]; 419 if (method == null) { 420 continue; 421 } 422 // skip static methods. 423 int mods = method.getModifiers(); 424 if (Modifier.isStatic(mods)) { 425 continue; 426 } 427 if (method.getName().equals(methodName) 428 && method.getParameterTypes().length == argCount) { 429 return method; 430 } 431 } 432 } 433 434 // Now check any inherited interfaces. This is necessary both when 435 // the argument class is itself an interface, and when the argument 436 // class is an abstract class. 437 Class ifcs[] = start.getInterfaces(); 438 for (int i = 0; i < ifcs.length; i++) { 439 Method m = internalFindMethod(ifcs[i], methodName, argCount); 440 if (m != null) { 441 return m; 442 } 443 } 444 445 return null; 446 } 447 448 /*** 449 * Internal support for finding a target methodName with a given 450 * parameter list on a given class. 451 */ 452 private static Method internalFindMethod( 453 Class start, 454 String methodName, 455 int argCount, 456 Class args[]) { 457 // For overriden methods we need to find the most derived version. 458 // So we start with the given class and walk up the superclass chain. 459 for (Class cl = start; cl != null; cl = cl.getSuperclass()) { 460 Method methods[] = getPublicDeclaredMethods(cl); 461 for (int i = 0; i < methods.length; i++) { 462 Method method = methods[i]; 463 if (method == null) { 464 continue; 465 } 466 // skip static methods. 467 int mods = method.getModifiers(); 468 if (Modifier.isStatic(mods)) { 469 continue; 470 } 471 // make sure method signature matches. 472 Class params[] = method.getParameterTypes(); 473 if (method.getName().equals(methodName) 474 && params.length == argCount) { 475 boolean different = false; 476 if (argCount > 0) { 477 for (int j = 0; j < argCount; j++) { 478 if (params[j] != args[j]) { 479 different = true; 480 continue; 481 } 482 } 483 if (different) { 484 continue; 485 } 486 } 487 return method; 488 } 489 } 490 } 491 492 // Now check any inherited interfaces. This is necessary both when 493 // the argument class is itself an interface, and when the argument 494 // class is an abstract class. 495 Class ifcs[] = start.getInterfaces(); 496 for (int i = 0; i < ifcs.length; i++) { 497 Method m = internalFindMethod(ifcs[i], methodName, argCount); 498 if (m != null) { 499 return m; 500 } 501 } 502 503 return null; 504 } 505 506 /*** 507 * Find a target methodName on a given class. 508 */ 509 static Method findMethod(Class cls, String methodName, int argCount) 510 throws IntrospectionException { 511 if (methodName == null) { 512 return null; 513 } 514 515 Method m = internalFindMethod(cls, methodName, argCount); 516 if (m != null) { 517 return m; 518 } 519 520 // We failed to find a suitable method 521 throw new IntrospectionException( 522 "No method \"" + methodName + "\" with " + argCount + " arg(s)"); 523 } 524 525 /*** 526 * Find a target methodName with specific parameter list on a given class. 527 */ 528 static Method findMethod( 529 Class cls, 530 String methodName, 531 int argCount, 532 Class args[]) 533 throws IntrospectionException { 534 if (methodName == null) { 535 return null; 536 } 537 538 Method m = internalFindMethod(cls, methodName, argCount, args); 539 if (m != null) { 540 return m; 541 } 542 543 // We failed to find a suitable method 544 throw new IntrospectionException( 545 "No method \"" 546 + methodName 547 + "\" with " 548 + argCount 549 + " arg(s) of matching types."); 550 } 551 552 /*** 553 * Return true if class a is either equivalent to class b, or 554 * if class a is a subclass of class b, ie if a either "extends" 555 * or "implements" b. 556 * Note tht either or both "Class" objects may represent interfaces. 557 */ 558 static boolean isSubclass(Class a, Class b) { 559 // We rely on the fact that for any given java class or 560 // primtitive type there is a unqiue Class object, so 561 // we can use object equivalence in the comparisons. 562 if (a == b) { 563 return true; 564 } 565 566 if (a == null || b == null) { 567 return false; 568 } 569 570 for (Class x = a; x != null; x = x.getSuperclass()) { 571 if (x == b) { 572 return true; 573 } 574 575 if (b.isInterface()) { 576 Class interfaces[] = x.getInterfaces(); 577 for (int i = 0; i < interfaces.length; i++) { 578 if (isSubclass(interfaces[i], b)) { 579 return true; 580 } 581 } 582 } 583 } 584 585 return false; 586 } 587 588 /*** 589 * Return true iff the given method throws the given exception. 590 */ 591 592 private boolean throwsException(Method method, Class exception) { 593 594 Class exs[] = method.getExceptionTypes(); 595 for (int i = 0; i < exs.length; i++) { 596 if (exs[i] == exception) { 597 return true; 598 } 599 } 600 601 return false; 602 } 603 }

This page was automatically generated by Maven